package com.naiterui.ehp.bs.doctor.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdcardUtil;
import com.google.common.collect.Lists;
import com.naiterui.ehp.bp.bo.agent.DoctorDetailBO;
import com.naiterui.ehp.bp.bo.consult.VideoConsultBO;
import com.naiterui.ehp.bp.bo.doctor.DoctorBO;
import com.naiterui.ehp.bp.bo.doctor.LoginParamBO;
import com.naiterui.ehp.bp.bo.doctor.PcUserBO;
import com.naiterui.ehp.bp.bo.doctor.RecomDoctorBO;
import com.naiterui.ehp.bp.bo.esign.EsignDoctorBO;
import com.naiterui.ehp.bp.constants.CommonConstantEnum;
import com.naiterui.ehp.bp.constants.ImConstants;
import com.naiterui.ehp.bp.domain.City;
import com.naiterui.ehp.bp.domain.ConsultSession;
import com.naiterui.ehp.bp.domain.Department;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.DrAccountOperateLog;
import com.naiterui.ehp.bp.domain.DrChargeConfig;
import com.naiterui.ehp.bp.domain.DrConsultComment;
import com.naiterui.ehp.bp.domain.DrMessageConsultOrder;
import com.naiterui.ehp.bp.domain.DrVideoConsult;
import com.naiterui.ehp.bp.domain.Hospital;
import com.naiterui.ehp.bp.domain.Licence;
import com.naiterui.ehp.bp.domain.Login;
import com.naiterui.ehp.bp.domain.NoticeInfo;
import com.naiterui.ehp.bp.domain.NoticeTopic;
import com.naiterui.ehp.bp.support.exception.BaseExceptionCodes;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.PublicService;
import com.naiterui.ehp.bp.utils.beans.BeanUtil;
import com.naiterui.ehp.bp.utils.biz.NameUtils;
import com.naiterui.ehp.bp.utils.biz.TitleUtil;
import com.naiterui.ehp.bp.utils.encypt.GengerCode;
import com.naiterui.ehp.bp.utils.encypt.MD5;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.lang.StringUtil;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import com.naiterui.ehp.bs.doctor.dao.ICityDao;
import com.naiterui.ehp.bs.doctor.dao.IDepartmentDao;
import com.naiterui.ehp.bs.doctor.dao.IDoctorDao;
import com.naiterui.ehp.bs.doctor.dao.IDrAccountOperateLogDao;
import com.naiterui.ehp.bs.doctor.dao.IHospitalDao;
import com.naiterui.ehp.bs.doctor.dao.ILoginDao;
import com.naiterui.ehp.bs.doctor.dao.INoticeInfoDao;
import com.naiterui.ehp.bs.doctor.dao.INoticeLabelDao;
import com.naiterui.ehp.bs.doctor.dao.INoticeTopicDao;
import com.naiterui.ehp.bs.doctor.dao.ITaxAuditDao;
import com.naiterui.ehp.bs.doctor.dao.user.ILicenceDao;
import com.naiterui.ehp.bs.doctor.domain.DoctorLabel;
import com.naiterui.ehp.bs.doctor.domain.TaxCertificationAudit;
import com.naiterui.ehp.bs.doctor.exception.ExceptionCodes;
import com.naiterui.ehp.bs.doctor.fegin.EsignFeginClient;
import com.naiterui.ehp.bs.doctor.fegin.ImFeginClient;
import com.naiterui.ehp.bs.doctor.fegin.PatientFeignClient;
import com.naiterui.ehp.bs.doctor.fegin.VideoConsultFeignClient;
import com.naiterui.ehp.bs.doctor.repository.IConsultSessionRepository;
import com.naiterui.ehp.bs.doctor.repository.IDoctorRepository;
import com.naiterui.ehp.bs.doctor.repository.IDrConsultCommentRepository;
import com.naiterui.ehp.bs.doctor.repository.IDrMessageConsultOrderRepository;
import com.naiterui.ehp.bs.doctor.repository.ILicenceRepository;
import com.naiterui.ehp.bs.doctor.repository.ILoginRepository;
import com.naiterui.ehp.bs.doctor.repository.IPatientPersonDoctorRepository;
import com.naiterui.ehp.bs.doctor.service.IChargeService;
import com.naiterui.ehp.bs.doctor.service.ICityService;
import com.naiterui.ehp.bs.doctor.service.IDoctorChargeService;
import com.naiterui.ehp.bs.doctor.service.IDoctorService;
import com.naiterui.ehp.bs.doctor.service.IDrSerItemsService;
import com.naiterui.ehp.bs.doctor.service.ILoginService;
import com.naiterui.ehp.bs.doctor.service.IPAService;
import com.naiterui.ehp.bs.doctor.service.ITencentDetectService;
import com.naiterui.ehp.bs.doctor.service.user.IDoctorRecordService;
import com.naiterui.ehp.bs.doctor.service.user.ILicenceService;
import com.naiterui.ehp.bs.doctor.utils.AdTencentCloudSdkUtil;
import com.naiterui.ehp.bs.doctor.utils.CmsServiceUtil;
import com.naiterui.ehp.bs.doctor.utils.DoctorLabelUtil;
import com.naiterui.ehp.bs.doctor.utils.IMnoticeUtil;
import com.naiterui.ehp.bs.doctor.utils.ImageAlphaUtil;
import com.naiterui.ehp.bs.doctor.utils.ImageUrlUtil;
import com.naiterui.ehp.bs.doctor.utils.LimitValueConfig;
import com.naiterui.ehp.bs.doctor.utils.PropertyValueConstants;
import com.naiterui.ehp.bs.doctor.utils.RedisUtils;
import com.naiterui.ehp.bs.doctor.utils.SMSUtil;
import com.naiterui.ehp.bs.doctor.vo.ChargeVO;
import com.naiterui.ehp.bs.doctor.vo.DoctorAuthVO;
import com.naiterui.ehp.bs.doctor.vo.DoctorInfoExcelVO;
import com.naiterui.ehp.bs.doctor.vo.DoctorMineVO;
import com.naiterui.ehp.bs.doctor.vo.DoctorParaRequestVO;
import com.naiterui.ehp.bs.doctor.vo.DoctorVO;
import com.naiterui.ehp.bs.doctor.vo.LicenceVO;
import com.naiterui.ehp.bs.doctor.vo.MedicalLicenseVO;
import com.naiterui.ehp.bs.doctor.vo.RecordHospitalVO;
import com.naiterui.ehp.bs.doctor.vo.SignUserParamVO;
import com.naiterui.ehp.bs.doctor.vo.TencentFaceIdVO;
import com.naiterui.ehp.bs.doctor.vo.UserCenterVO;
import com.naiterui.ehp.bs.doctor.vo.inner.DoctorParamVO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static com.naiterui.ehp.bp.domain.Licence.DOCTOR_LICENCE_TYPE_AVATAR;
import static com.naiterui.ehp.bp.domain.Licence.DOCTOR_LICENCE_TYPE_IDCARD;

@Slf4j
@Service
public class AdDoctorServiceImpl implements IDoctorService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AdDoctorServiceImpl.class);

    public static final String SEAL_PATH_PRE_DR = ConfigUtil.getString("esign.seal.path") + "dr/";

    @Value("${ehp.domain.img}")
    private String imgDomain;
    @Autowired
    private ICityDao cityDao;
    @Autowired
    private ILoginDao loginDao;
    @Autowired
    private IDoctorDao doctorDao;
    @Autowired
    private IPAService paService;
    @Autowired
    private ICityService cityService;
    @Autowired
    private IHospitalDao hospitalDao;
    @Autowired
    private ILoginService loginService;

    @Autowired
    private IDoctorChargeService doctorChargeService;

    @Autowired
    private INoticeInfoDao noticeInfoDao;
    @Autowired
    private IDepartmentDao departmentDao;
    @Autowired
    private ILicenceService licenceService;
    @Autowired
    private INoticeTopicDao noticeTopicDao;

    @Autowired
    private IDoctorRecordService doctorRecordService;
    @Autowired
    private IDrAccountOperateLogDao drAccountOperateLogDao;

    @Autowired
    private INoticeLabelDao noticeLabelDao;

    @Autowired
    private EsignFeginClient esignFeginClient;

    @Autowired
    private ITaxAuditDao taxAuditDao;
    @Value("${file.base.prefix}")
    private String fileBasePre;

    @Autowired
    private ILoginRepository loginRepository;

    @Autowired
    private PatientFeignClient patientFeignClient;

    @Autowired
    private IDoctorRepository doctorRepository;
    @Autowired
    private IConsultSessionRepository consultSessionRepository;
    @Autowired
    private ImFeginClient imFeginClient;
    @Autowired
    private IDrSerItemsService drChargeItemsService;
    @Autowired
    private ILicenceDao licenceDao;
    @Autowired
    private VideoConsultFeignClient videoConsultFeignClient;
    @Autowired
    private IDrMessageConsultOrderRepository drMessageConsultOrderRepository;

    @Autowired
    private IChargeService chargeService;
    @Autowired
    private IPatientPersonDoctorRepository patientPersonDoctorRepository;
    @Autowired
    private IDrConsultCommentRepository drConsultCommentRepository;
    @Autowired
    private ILicenceRepository licenceRepository;

    @Autowired
    private ITencentDetectService tencentDetectService;

    public static final String HOSPITAL_NAME = ConfigUtil.getString("ehp.hospital.name");

    /**
     * 用户中心 DoctorServiceImpl.getUserCenter()
     *
     * @param doctorId 医生ID
     * @return
     * @throws BusinessException
     * @Author guoyongxiang
     * @Date 2016年1月13日
     * @since 2.0.0
     */
    @Override
    public UserCenterVO getUserCenter(Long doctorId) throws BusinessException {
        Doctor doctor = doctorDao.get(doctorId);
        if (doctor == null) {
            LOGGER.warn("医生不存在id:" + doctorId);
            throw new BusinessException(ExceptionCodes.DOCTORID_NOT_EXITS);
        }

        // 职称
        Integer titleRecord = null;
        String title = "";
        Integer titleId = doctor.getTitleId();
        if (titleId != null) {
            title = TitleUtil.getTitle(titleId);
            if (titleId.equals(TitleUtil.Enum.PHYSICIAN.getId()) || titleId.equals(TitleUtil.Enum.MAIN_HOSPITAL_PHYSICIAN.getId())) {
                titleRecord = UserCenterVO.RECORD_CAN_NOT;
            } else {
                titleRecord = UserCenterVO.RECORD_CAN;
            }
        }

        // 认证状态
        Integer status = doctor.getStatus();
        if (status == null) {
            status = Doctor.AUTH_STATUS_NOT_PERFECTION;
        }

        // 是否显示"我的积分"入口
        Integer showPoint = UserCenterVO.SHOW_POINT_NO;
        // 默认头像
        String headerImageUrl = ImageUrlUtil.getImagePath(UserCenterVO.DEFAULT_HEAD_IMG);
        if (status == Doctor.AUTH_STATUS_SUCCESS) {
            headerImageUrl = ImageUrlUtil.getImagePath(doctor.getHeadUrl());
            showPoint = UserCenterVO.SHOW_POINT_YES;
        }

        // v2.2暂时取消账户相关内容,by gyxs
        Integer recordStatus = doctor.getRecordStatus();
        UserCenterVO userCenterVO = new UserCenterVO(doctor.getName(), headerImageUrl, title, doctor.getStatus(), recordStatus);
        userCenterVO.setTitleRecord(titleRecord);
        // 提交过认证资料的医生可以申请备案
        userCenterVO.setRecordable(status == Doctor.AUTH_STATUS_NOT_PERFECTION ? UserCenterVO.RECORD_CAN_NOT : UserCenterVO.RECORD_CAN);
        userCenterVO.setIsPublic(doctor.validateLabel(Doctor.DOCTOR_LABEL_PUBLIC) ? Doctor.PUBLIC_STATUS_YES : Doctor.PUBLIC_STATUS_NO);// 阳光化标示
        userCenterVO.setShowPoint(showPoint);
        userCenterVO.setTotalPoint(0);
        userCenterVO.setSealImage(StringUtils.isNotEmpty(doctor.getSealImage()) ? imgDomain + doctor.getSealImage() : "");
        // =================================end========================

        setTaxAuditStatus(doctorId, userCenterVO);

        //认证通过后获取医生实名状态
        if (Doctor.AUTH_STATUS_SUCCESS == doctor.getStatus()) {
            //获取实名认证状态
            EsignDoctorBO bo = esignFeginClient.getEsignInfo(doctorId);
            userCenterVO.setRealnameStatus(bo.getAuthStatus());
        }

        // 累计问诊数量
        List<VideoConsultBO> videoConsultOrders = videoConsultFeignClient.getOrderByDoctorId(doctorId);
        Integer videoConsultCount = videoConsultOrders.size();
        Integer textConsultCount =
            Optional.ofNullable(consultSessionRepository.findCountByDoctorId(doctorId)).orElse(0);
        userCenterVO.setConsultNum(textConsultCount + videoConsultCount);

        // 累计收入(图文收入+视频收入)
        List<DrMessageConsultOrder> textConsultOrders = drMessageConsultOrderRepository.findByDoctorId(doctorId);
        Integer allConsultPrice = textConsultOrders.stream().mapToInt(o -> o.getPrice()).sum();
        allConsultPrice +=
            videoConsultOrders.stream().filter(v -> DrVideoConsult.VIDEO_CONSULT_PAY_SUCCESS == v.getPayStatus())
                .mapToInt(VideoConsultBO::getPrice).sum();
        userCenterVO.setAllConsultPrice(allConsultPrice);

        // 本月收入(图文收入+视频收入)
        int monthConsultPrice =
            textConsultOrders.stream().filter(o -> DateUtil.month(o.getCreatedAt()) == DateUtil.thisMonth())
                .mapToInt(DrMessageConsultOrder::getPrice).sum();
        monthConsultPrice += videoConsultOrders.stream()
            .filter(v -> DrVideoConsult.VIDEO_CONSULT_PAY_SUCCESS == v.getPayStatus()
                && DateUtil.month(v.getCreatedAt()) == DateUtil.thisMonth())
            .mapToInt(VideoConsultBO::getPrice).sum();
        userCenterVO.setMonthConsultPrice(monthConsultPrice);


        // 设置医院
        if (doctor.getHospitalId() != null) {
            String hospital = null;
            // 自定义医院
            if (doctor.getHospitalId().equals(0)) {
                hospital = doctor.getHospitalName();
            } else {
                Hospital hospitalObj = hospitalDao.get(doctor.getHospitalId());
                if (hospitalObj != null) {
                    hospital = hospitalObj.getName();
                }
            }
            userCenterVO.setHospitalName(hospital);
        }

        // 科室信息
        if (doctor.getDepartmentId() != null) {
            String department = "";
            if (doctor.getDepartmentId() == 0) {
                department = doctor.getCustomDepartment();
            } else {
                Department departmentObj = departmentDao.get(doctor.getDepartmentId());
                if (departmentObj != null) {
                    department = departmentObj.getName();
                }
            }
            userCenterVO.setDepartment(department);
            userCenterVO.setDepartmentId(doctor.getDepartmentId());
        }
        userCenterVO.setPhone(doctor.getPhone());
        userCenterVO.setExpertise(doctor.getExpertise());
        userCenterVO.setIntroduction(doctor.getIntroduction());
        return userCenterVO;
    }

    /**
     * 设置缴税实名认证相关状态
     */
    private void setTaxAuditStatus(Long doctorId, UserCenterVO userCenterVO) throws BusinessException {
        TaxCertificationAudit taxAudit = taxAuditDao.getTaxAudit(doctorId);

        Integer consentShow = UserCenterVO.SHOW_CONSENT_YES;
        // 审核状态
        if (taxAudit != null) {
            userCenterVO.setTaxStatus(taxAudit.getAuditStatus());
            // 是否已同意
            if (taxAudit.getInformedConsent() == UserCenterVO.SHOW_CONSENT_YES) {
                consentShow = UserCenterVO.SHOW_CONSENT_NO;
            }
        } else {
            userCenterVO.setTaxStatus(TaxCertificationAudit.NOT_APPLY);
        }

        // 银川已备案医生
        if (consentShow == UserCenterVO.SHOW_CONSENT_YES) {
            RecordHospitalVO doctorRecordHospital = doctorRecordService.getDoctorRecordHospital(doctorId);
            /*if (!PCNews.HOSPITAL_TYPE_YINCHUAN.equals(doctorRecordHospital.getId())) {
                consentShow = UserCenterVO.SHOW_CONSENT_NO;
            }*/
        }
        userCenterVO.setIsConsentShow(consentShow);

        // 身份证信息是否缺失
        Licence licence = licenceService.getLicenceByType(doctorId, Licence.DOCTOR_LICENCE_TYPE_IDCARD);
        if (licence == null || licence.getNumber() == null || licence.getUrl() == null || !licence.getUrl().contains(",")) {
            userCenterVO.setIdCardStatus(UserCenterVO.ID_CARD_NOT);
        } else {
            userCenterVO.setIdCardStatus(UserCenterVO.ID_CARD_YES);
        }

    }

    @Override
    public DoctorVO getDoctor(Long doctorId) throws BusinessException {
        Doctor doctor = doctorDao.get(doctorId);

        if (doctor == null) {
            LOGGER.warn("医生ID不存在 id=" + doctorId);
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }

        DoctorVO doctorVO = new DoctorVO();
        doctorVO.setDoctorId(doctorId);
        doctorVO.setName(doctor.getName());
        doctorVO.setPhone(doctor.getPhone());
        doctorVO.setEmail(doctor.getEmail());

        // 设置职称.
        if (doctor.getTitleId() != null) {
            String title = TitleUtil.getTitle(doctor.getTitleId());
            doctorVO.setTitle(title);
            doctorVO.setTitleId(doctor.getTitleId());
        }

        // 设置性别
        if (doctor.getGender() != null) {
            String gender = CommonConstantEnum.GENDER_FEMALE.getId().equals(doctor.getGender()) ? CommonConstantEnum.GENDER_FEMALE.getName()
                : CommonConstantEnum.GENDER_MALE.getName();
            doctorVO.setGender(gender);
        }

        // 设置科室
        if (doctor.getDepartmentId() != null) {
            String department = "";
            if (doctor.getDepartmentId() == 0) {
                department = doctor.getCustomDepartment();
            } else {
                Department departmentObj = departmentDao.get(doctor.getDepartmentId());
                if (departmentObj != null) {
                    department = departmentObj.getName();
                }
            }
            doctorVO.setDepartment(department);
            doctorVO.setDepartmentId(doctor.getDepartmentId());
        }

        // 设置医院
        if (doctor.getHospitalId() != null) {
            String hospital = null;
            // 自定义医院
            if (doctor.getHospitalId().equals(0)) {
                hospital = doctor.getHospitalName();
            } else {
                Hospital hospitalObj = hospitalDao.get(doctor.getHospitalId());
                if (hospitalObj != null) {
                    hospital = hospitalObj.getName();
                }
            }
            doctorVO.setHospitalId(doctor.getHospitalId());
            doctorVO.setHospital(hospital);
        }

        doctorVO.setCityId(doctor.getCityId());
        doctorVO.setExpertise(doctor.getExpertise());
        doctorVO.setIntroduction(doctor.getIntroduction());

        Integer status = doctor.getStatus();
        doctorVO.setStatus(status);

        //2021-04-20新增个人签名图片
        if (StringUtils.isNotBlank(doctor.getSealImage())) {
            doctorVO.setSealImage(ImageUrlUtil.getImagePath(doctor.getSealImage()));
        }

        // 医生 个人信息查询 添加认证不通过原因 、审核中描述 change by Gaoll 2016年2月29日
        String authDesc = "";
        if (Doctor.AUTH_STATUS_REJECTED == status) {
            DrAccountOperateLog lastRejected = drAccountOperateLogDao.getLastRejected(doctorId);
            if (null != lastRejected) {
                authDesc = ConfigUtil.getString("doctor.auth.fail.content") + lastRejected.getReason();
            }
        } else if (Doctor.AUTH_STATUS_AUDITING == doctor.getStatus()) {
            authDesc = ConfigUtil.getString("doctor.auth.content");
        }
        doctorVO.setFailReason(authDesc);

        if (status == Doctor.AUTH_STATUS_SUCCESS) {

            doctorVO.setUploadStatus(DoctorVO.DOCTOR_LICENCE_STATUS_EXIST);

        } else {

            Map<Integer, LicenceVO> licences = licenceService.getLicencesByType(doctorId, DOCTOR_LICENCE_TYPE_AVATAR,
                Licence.DOCTOR_LICENCE_TYPE_EMCARD,
                Licence.DOCTOR_LICENCE_TYPE_MEDICAL);

            // 个人照片
            LicenceVO avatar = licences.get(DOCTOR_LICENCE_TYPE_AVATAR);
            String avatarUrl = avatar != null && !avatar.getUrls().isEmpty() ? avatar.getUrls().get(0) : null;

            // 工作证
            LicenceVO emCard = licences.get(Licence.DOCTOR_LICENCE_TYPE_EMCARD);
            String emCardUrl = emCard != null && !emCard.getUrls().isEmpty() ? emCard.getUrls().get(0) : null;

            // 职业资格证
            MedicalLicenseVO medicalLicense = (MedicalLicenseVO) licences.get(Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
            if (StringUtils.isNoneBlank(avatarUrl, emCardUrl) && medicalLicense.validateFileNum()) {

                doctorVO.setUploadStatus(DoctorVO.DOCTOR_LICENCE_STATUS_EXIST);

            } else {

                doctorVO.setUploadStatus(DoctorVO.DOCTOR_LICENCE_STATUS_NOTEXIST);
            }

        }

        return doctorVO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveDoctor(DoctorParaRequestVO doctorParaRequestVO, String verifyCode, String password,
        Integer delType) throws BusinessException {

        Long doctorId = doctorParaRequestVO.getDoctorId();

        Doctor doctorDB = doctorDao.get(doctorId);
        if (doctorDB == null) {
            LOGGER.warn("医生ID不存在 id:{}", doctorId);
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }

        // 手机号
        if (!StringUtils.isEmpty(doctorParaRequestVO.getPhone())) {
            if (StringUtils.isEmpty(verifyCode)) {
                LOGGER.warn("短信验证码不能为空");
                throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
            }

            String phoneNum = doctorParaRequestVO.getPhone();
            String redisKey = PropertyValueConstants.PHONE_MODIFY_PHONE_KEY + phoneNum;
            try {
                String cacheVerifyCode = (String) RedisUtils.get(redisKey);
                if (!verifyCode.trim().equalsIgnoreCase(cacheVerifyCode)) {
                    if (loginService.validUserOperator(redisKey)) {
                        throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_ERROR);
                    }
                    LOGGER.error("医生修改手机号时验证码错误,phoneNum=" + phoneNum + ",输入验证码为:" + verifyCode + ",缓存中验证码为:" + cacheVerifyCode);
                    throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_ERROR);
                }
            } catch (Exception e) {
                LOGGER.error("从redis获取注册医生验证码失败,phoneNum={}", phoneNum, e);
                throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_ERROR);
            }

            Login login = loginDao.get(doctorDB.getUserId());
            if (login == null) {
                LOGGER.warn("医生ID不存在 id={}", doctorId);
                throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
            }

            if (!MD5.md5(password.trim() + login.getSalt()).equals(login.getPassword())) {
                LOGGER.warn("密码不正确 doctorId:{}", doctorId);
                throw new BusinessException(ExceptionCodes.LOGIN_MODIFY_PWD_FAIL);
            }

            String phone = doctorParaRequestVO.getPhone();

            boolean exist = loginDao.findCountByPhoneNum(phoneNum);
            if (exist) {
                throw new BusinessException(ExceptionCodes.REGIST_EXSIT);
            }

            // 修改电子签名预留手机号
            if (!doctorDB.getPhone().equals(phone) && Doctor.AUTH_STATUS_SUCCESS == doctorDB.getStatus()) {
                esignFeginClient.esignDoctorUpdatePhone(doctorId, phone);
            }

            doctorDB.setPhone(phone);
            login.setPhoneNum(phone);
            login.setToken(null);
            login.setChangedAt(new Date());
            loginDao.updateLogin(login);
            RedisUtils.delete(redisKey);
        }

        // 处理医生必填信息
        handleBaseInfo(doctorParaRequestVO, doctorDB);

        // 根据删除类型,清除擅长或者个人简介
        if (delType != null) {
            if (delType == PropertyValueConstants.DOCTOR_DEL_TYPE_EXPERTISE) {
                doctorDB.setExpertise("");
            } else if (delType == PropertyValueConstants.DOCTOR_DEL_TYPE_INTRODUCTION) {
                doctorDB.setIntroduction("");
            }
        }
        // 添加变更时间
        doctorDB.setChangedAt(new Date());
        doctorDao.update(doctorDB);
        // JIRA 5148 注册修改医院信息时 修改缓存
        if (null != doctorDB.getHospitalId()) {
            // 将医生所在城市修改后加入到缓存
            // 根据医生id 去拿key的名字
            String doctorAreaKey = PropertyValueConstants.getDoctorAreaCache(doctorId);
            Integer cityId = getDoctorAreaId(doctorDB);
            // 修改医生所在区域缓存
            RedisUtils.set(doctorAreaKey, cityId, PropertyValueConstants.DOCTOR_AREA_EXPIRE);
        }

    }

    /**
     * 处理医生必填信息 DoctorServiceImpl.handleBaseInfo()
     *
     * @param doctorParaRequestVO 参数实体对象
     * @param doctorDB            待存储对象
     * @throws BusinessException
     * @Date 2016年3月6日
     * @since 2.2.0
     */
    private void handleBaseInfo(DoctorParaRequestVO doctorParaRequestVO, Doctor doctorDB) throws BusinessException {

        // 姓名
        if (!StringUtils.isEmpty(doctorParaRequestVO.getName())) {
            if (doctorDB.getStatus() != null && Doctor.AUTH_STATUS_SUCCESS == doctorDB.getStatus()) {
                LOGGER.warn("用户已认证，不允许修改姓名doctorId=" + doctorParaRequestVO.getDoctorId());
                throw new BusinessException(ExceptionCodes.DOCTOR_INFO_MODIFY_DENIED);
            }
            // 2.2.0需求：医生姓名只允许 中文、英文大小写、数字、英文句号(.)
            boolean nameVerify = LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_REALNAME, doctorParaRequestVO.getName());
            if (!nameVerify) {
                LOGGER.error("doctor login regist ERROR ; name length not allowed : name {}", doctorParaRequestVO.getName());
                throw new BusinessException(ExceptionCodes.INVALID_CONTENT_LENGTH);
            }
            if (!doctorParaRequestVO.getName().trim().matches(NameUtils.VALIDATE_REGEX_NAME)) {
                LOGGER.error("handleBaseInfo ERROR ; name format not allowed : name {}", doctorParaRequestVO.getName());
                throw new BusinessException(ExceptionCodes.EMOJI_ERROR);
            }
            doctorDB.setName(doctorParaRequestVO.getName().trim());
        }
        // 性别
        if (null != doctorParaRequestVO.getGender()) {
            doctorDB.setGender(doctorParaRequestVO.getGender());
        }
        // 职称
        if (null != doctorParaRequestVO.getTitleId()) {
            doctorDB.setTitleId(doctorParaRequestVO.getTitleId());
        }
        // 科室
        if (null != doctorParaRequestVO.getDepartmentId()) {
            doctorDB.setDepartmentId(doctorParaRequestVO.getDepartmentId());
            if (doctorParaRequestVO.getDepartmentId() == 0) {
                // 2.2.0需求：自定义科室，中英文，常用符号，长度20
                if (!doctorParaRequestVO.getCustomDepartment().trim().matches(NameUtils.VALIDATE_REGEX_DEPARTMENT)) {
                    LOGGER.error(" handleBaseInfo ERROR ; customDepartment format not allowed : customDepartment {}",
                        doctorParaRequestVO.getCustomDepartment());
                    throw new BusinessException(ExceptionCodes.EMOJI_ERROR);
                }
                doctorDB.setCustomDepartment(doctorParaRequestVO.getCustomDepartment());
            }
        }
        // 医院
        if (null != doctorParaRequestVO.getHospitalId()) {
            if (doctorParaRequestVO.getHospitalId().equals(0)) {
                if (null == doctorParaRequestVO.getCityId() || doctorParaRequestVO.getCityId() == 0
                    || StringUtils.isBlank(doctorParaRequestVO.getHospitalName())) {
                    LOGGER.warn("城市ID或医院名称为空 doctoId=" + doctorParaRequestVO.getDoctorId() + ",cityId=" + doctorParaRequestVO.getCityId()
                        + ",hospitalName=" + doctorParaRequestVO.getHospitalName());
                    throw new BusinessException(ExceptionCodes.PARAM_ERROR);
                }
                // 2.2.0需求：自定义医院，中英文，常用符号，长度40
                if (!doctorParaRequestVO.getHospitalName().trim().matches(NameUtils.VALIDATE_REGEX_HOSIPITAL)) {
                    LOGGER.error(" handleBaseInfo ERROR ; hospitalName format not allowed : hospitalName {}", doctorParaRequestVO.getHospitalName());
                    throw new BusinessException(ExceptionCodes.EMOJI_ERROR);
                }
                doctorDB.sethospitalName(doctorParaRequestVO.getHospitalName());
                doctorDB.setCityId(doctorParaRequestVO.getCityId());
                doctorDB.setHospitalId(doctorParaRequestVO.getHospitalId());
            } else {
                Hospital hospital = hospitalDao.get(doctorParaRequestVO.getHospitalId());
                if (hospital != null) {

                    // 城市信息编辑保存，联动业务区域调整 change by Gaoll 2016-06-02
                    City hospitalCity = cityDao.get(hospital.getCityId());
                    if (null != hospitalCity) {

                        // 县级城市处理使用其上级ID，非县级城市使用该城市ID
                        Integer targetCityId = City.TYPE_REGI0N == hospitalCity.getType() ? hospitalCity.getParentId() : hospitalCity.getId();

                        doctorDB.setCityId(targetCityId);
                    }
                }
                doctorDB.setHospitalId(doctorParaRequestVO.getHospitalId());
            }
        }

        // 专长
        if (StringUtils.isNotBlank(doctorParaRequestVO.getExpertise())) {
            String expertise = handleBlank(doctorParaRequestVO.getExpertise());
            if (!LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_EXPERTISE, expertise)) {
                LOGGER.error("saveDoctor ERROR ; expertise length not allowed : expertise {}", expertise);
                throw new BusinessException(ExceptionCodes.INVALID_CONTENT_LENGTH);
            }
            /*if (!expertise.trim().matches(NameUtils.VALIDATE_REGEX_OTHERS)) {
                LOGGER.error("saveDoctor ERROR ; expertise format not allowed : expertise {}", doctorParaRequestVO.getExpertise());
                throw new BusinessException(ExceptionCodes.EMOJI_ERROR);
            }*/
            doctorDB.setExpertise(expertise);
        }

        // 简介
        if (StringUtils.isNotBlank(doctorParaRequestVO.getIntroduction())) {
            String dntroduction = handleBlank(doctorParaRequestVO.getIntroduction());
            if (!LimitValueConfig.validateLimit(LimitValueConfig.LIMIT_INTRODUCTION, dntroduction)) {
                LOGGER.error("saveDoctor ERROR ; dntroduction length not allowed : dntroduction {}", dntroduction);
                throw new BusinessException(ExceptionCodes.INVALID_CONTENT_LENGTH);
            }
            /*if (!dntroduction.trim().matches(NameUtils.VALIDATE_REGEX_OTHERS)) {
                LOGGER.error("saveDoctor ERROR ; introduction format not allowed : introduction {}", doctorParaRequestVO.getIntroduction());
                throw new BusinessException(ExceptionCodes.EMOJI_ERROR);
            }*/
            doctorDB.setIntroduction(dntroduction);
        }
    }

    @Override
    public DoctorAuthVO getDoctorAuthByDoctorId(Long doctorId) throws BusinessException {

        DoctorAuthVO auth = new DoctorAuthVO();
        auth.setDoctorId(doctorId);
        Doctor doctor = doctorDao.get(doctorId);
        if (doctor == null) {
            LOGGER.warn("医生ID不存在 id=" + doctorId);
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }
        // 姓名
        auth.setName(doctor.getName());

        // 设置职称
        if (doctor.getTitleId() != null) {
            String title = TitleUtil.getTitle(doctor.getTitleId());
            auth.setTitle(title);
        }

        // 设置科室
        if (doctor.getDepartmentId() != null) {
            String department = "";
            if (doctor.getDepartmentId() == 0) {
                department = doctor.getCustomDepartment();
            } else {
                Department departmentObj = departmentDao.get(doctor.getDepartmentId());
                if (departmentObj != null) {
                    department = departmentObj.getName();
                }
            }
            auth.setDepartment(department);
        }

        auth.setStatus(doctor.getStatus());

        Map<Integer, LicenceVO> licences = licenceService.getLicencesByType(doctorId, DOCTOR_LICENCE_TYPE_AVATAR,
            Licence.DOCTOR_LICENCE_TYPE_EMCARD,
            Licence.DOCTOR_LICENCE_TYPE_MEDICAL);

        // 个人照片
        LicenceVO avatar = licences.get(DOCTOR_LICENCE_TYPE_AVATAR);
        auth.setAvatar(avatar != null && !avatar.getUrls().isEmpty() ? avatar.getUrls().get(0) : null);

        // 工作证
        LicenceVO emCard = licences.get(Licence.DOCTOR_LICENCE_TYPE_EMCARD);
        auth.setEmcard(emCard != null && !emCard.getUrls().isEmpty() ? emCard.getUrls().get(0) : null);

        // 职业资格证
        MedicalLicenseVO medicalLicense = (MedicalLicenseVO) licences.get(Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
        auth.setMedicalLicense(medicalLicense);

        //个人签名图片
        if (StringUtils.isNotBlank(doctor.getSealImage())) {
            auth.setSealImage(ImageUrlUtil.getImagePath(doctor.getSealImage()));
        }

        // 资质图片上传状态(0:未上传,1:已上传)
        // http://jira.sw.naiterui.com/browse/DBYZ-2512 要求只有上传了3张才显示已上传
        // V2.9.0,改版后，图片、工作证、职业资格证图片资料完全上传才为已上传。（职业资格证，新版一张图，旧版两张图） Amber 2017-08-01

        Integer status = doctor.getStatus();
        // 认证失败，需要添加失败原因
        if (status == Doctor.AUTH_STATUS_REJECTED) {
            DrAccountOperateLog lastRejected = drAccountOperateLogDao.getLastRejected(doctorId);
            if (lastRejected != null) {
                auth.setFailReason(lastRejected.getReason());
            }
        }

        // 如果已经认证通过，那么就任务资料已经全部上传
        if (status == Doctor.AUTH_STATUS_SUCCESS) {

            auth.setUploadStatus(DoctorVO.DOCTOR_LICENCE_STATUS_EXIST);

        } else if (StringUtils.isNotBlank(auth.getAvatar()) && StringUtils.isNotBlank(auth.getEmcard())
            && auth.getMedicalLicense().validateFileNum()) {

            auth.setUploadStatus(DoctorVO.DOCTOR_LICENCE_STATUS_EXIST);

        } else {

            auth.setUploadStatus(DoctorVO.DOCTOR_LICENCE_STATUS_NOTEXIST);
        }

        return auth;
    }

    @Override
    public Doctor getDoctorById(Long doctorId) {
        return doctorDao.get(doctorId);
    }

    @Override
    public Doctor findDoctorByUserId(Long userId) {
        return doctorDao.getDoctorByUserId(userId);
    }

    @Override
    public Doctor getDoctorByPhone(String phoneNum) {
        return doctorDao.getDoctorByPhone(phoneNum);
    }


    /*
     * 医生端2.0,新注册用户、老用户新版本首登，初始化公告信息
     * IDoctorService.getNewDoctorNotice()
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void getNewDoctorNotice(Long doctorId, Integer initType) throws Exception {

        if (initType == 1) {
            // 初始化注册成功提示语，出发时机调整为首次登陆互联网医院
            paService.savePaWithNotice(doctorId, null, PropertyValueConstants.TEMPLATE_TYPE_REGIST, null);
        }
        Doctor doctor = doctorDao.get(doctorId);
        Date beginTime = doctor.getCreatedAt();
        List<NoticeInfo> noticeList = noticeInfoDao.getUserNotice(beginTime);
        // 2.13新增
        // 发送消息中间实体
        List<NoticeInfo> nList = new ArrayList<>();
        if (noticeList.size() > 0) {
            for (NoticeInfo noticeInfo : noticeList) {
                if (!noticeInfo.getIsLabel()) {
                    NoticeInfo info = new NoticeInfo();
                    BeanUtils.copyProperties(noticeInfo, info);
                    nList.add(info);
                } else {
                    // 判断该医生是否符合该标签
                    List<Long> ids = noticeLabelDao.getLabelIds(noticeInfo.getId());
                    if (ids.size() > 0) {
                        NoticeInfo info = new NoticeInfo();
                        // 根据医生id及bannerI判断 该医生是否包含此标签
                        Boolean result = CmsServiceUtil.drContainLabelId(doctorId, ids);
                        if (result) {
                            BeanUtils.copyProperties(noticeInfo, info);
                            nList.add(info);
                        }

                    }

                }
            }
        }
        Map<Long, NoticeTopic> topicMap = noticeTopicDao.getTopicMap();
        // 系统公告-单人-推送
        if (!nList.isEmpty()) {
            for (NoticeInfo noticeInfo : nList) {
                IMnoticeUtil.singleTopicNotice(doctorId, topicMap.get(noticeInfo.getTopicId()), noticeInfo.getTitle());
            }
        }
        // 2.17 新注册用户推送新手教程
        //        if (initType == 1) {
        //            drNoticeInfoService.saveNewDoctorMsg(doctorId);
        //        }
    }

    /**
     * 更新用户签章图
     *
     * @param doctorId
     * @param sealImg
     * @return
     */
    @Override
    public void saveSealImg(Long doctorId, MultipartFile sealImg) throws BusinessException {
        Doctor doctorDB = doctorDao.get(doctorId);
        if (null == doctorDB) {
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }
        // 保存签章图
        if (sealImg != null) {
            LOGGER.info("上传医生签名图片 doctorId:{}, fileName:{}", doctorId, sealImg.getName());
            // 生成签名印章
            String filepath = SEAL_PATH_PRE_DR + doctorId + "_" + System.currentTimeMillis() + "_o.jpg";
            LOGGER.info("上传医生签名图片地址 {}", filepath);
            File signImgfile = new File(filepath);
            try {
                FileUtils.forceMkdirParent(signImgfile);
                FileUtils.copyInputStreamToFile(sealImg.getInputStream(), signImgfile);
            } catch (IOException e) {
                throw new BusinessException(ExceptionCodes.DOCTOR_SEAL_IMG_FAIL);
            }
            String path = filepath.replaceAll(this.fileBasePre, "");
            LOGGER.info("替换路径前缀后地址 {}", filepath);
            doctorDB.setSealImage(path);
            doctorDB.setChangedAt(new Date());
            doctorDao.update(doctorDB);

            // 电签用户签章图信息修改
            esignFeginClient.saveSealImg(doctorId, path);
        }
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveDoctorBase(DoctorParaRequestVO doctorParaRequestVO) throws BusinessException {

        Long doctorId = doctorParaRequestVO.getDoctorId();

        Doctor doctorDB = doctorDao.get(doctorId);

        if (doctorDB == null) {
            LOGGER.error("saveDoctorBase 医生ID不存在 id={}", doctorId);
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }

        int authStatus = doctorDB.getStatus();

        // 资料未提交、审核不通过（可编辑）
        if (Doctor.AUTH_STATUS_NOT_PERFECTION != authStatus && Doctor.AUTH_STATUS_REJECTED != authStatus) {
            LOGGER.error("saveDoctorBase ERROR ; doctor audit status not allowed : doctorId {}", doctorId);
            throw new BusinessException(ExceptionCodes.DOCTOR_INFO_MODIFY_DENIED);
        }
        String email = doctorParaRequestVO.getEmail();
        if (StringUtils.isNotBlank(email)) {
            if (!Validator.isEmail(email)) {
                throw new BusinessException(ExceptionCodes.DOCTOR_IS_EMAIL_ERROR);
            }
            doctorDB.setEmail(email);
        }
        handleBaseInfo(doctorParaRequestVO, doctorDB);
        MultipartFile sealImg = doctorParaRequestVO.getSealImg();
        String sealImgUrl = doctorParaRequestVO.getSealImgUrl();
        // 保存签章图
        if (sealImg != null) {
            LOGGER.info("上传医生签名图片 doctorId:{}, fileName:{}", doctorId, sealImg.getName());
            // 生成签名印章
            String filepath = SEAL_PATH_PRE_DR + doctorId + "_" + System.currentTimeMillis() + "_o.jpg";
            LOGGER.info("上传医生签名图片地址 {}", filepath);
            File signImgfile = new File(filepath);
            try {
                FileUtils.forceMkdirParent(signImgfile);
                FileUtils.copyInputStreamToFile(sealImg.getInputStream(), signImgfile);
            } catch (IOException e) {
                throw new BusinessException(ExceptionCodes.DOCTOR_SEAL_IMG_FAIL);
            }
            String path = filepath.replaceAll(this.fileBasePre, "");
            LOGGER.info("替换路径前缀后地址 {}", filepath);
            doctorDB.setSealImage(path);
        } else if (StringUtils.isNotBlank(sealImgUrl)) {
            doctorDB.setSealImage(sealImgUrl.replaceAll(imgDomain, ""));
        }
        doctorDB.setSubmitAuditTime(new Date());
        doctorDB.setChangedAt(new Date());
        doctorDao.update(doctorDB);
        // JIRA 5148 注册修改医院信息时 修改缓存 将医生所在城市修改后加入到缓存 根据医生id 去拿key的名字
        if (null != doctorDB.getHospitalId()) {
            String doctorAreaKey = PropertyValueConstants.getDoctorAreaCache(doctorId);
            Integer cityId = getDoctorAreaId(doctorDB);
            // 修改医生所在区域缓存
            RedisUtils.set(doctorAreaKey, cityId, PropertyValueConstants.DOCTOR_AREA_EXPIRE);
        }

        // 根据职级同步设置图文咨询服务费
        doctorChargeService.saveDefaultCharge(doctorDB, DrChargeConfig.TYPE_MESSAGE);
        // 根据职级同步设置视频问诊服务费
        doctorChargeService.saveDefaultCharge(doctorDB, DrChargeConfig.TYPE_VIDEO);

    }

    @Override
    public Map<Long, Doctor> getDoctorMap(Set<Long> doctorIds) {

        return doctorDao.getDoctorMap(doctorIds);
    }

    @Override
    public Doctor get(Long doctorId) throws BusinessException {
        // 获取医生
        Doctor doctor = doctorDao.get(doctorId);

        // 医生不存在
        if (doctor == null) {
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }

        // 账户是否被禁用
        if (doctor.getAccountStatus() == 1) {
            throw new BusinessException(ExceptionCodes.DOCTOR_ACCOUNT_NOT_NOMAL);
        }
        return doctor;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update(Doctor doctor) {
        doctorDao.saveOrUpdate(doctor);
    }

    @Override
    public RecomDoctorBO getInnerDoctor(Long doctorId) throws BusinessException {

        // 获取医生
        Doctor doctor = doctorDao.get(doctorId);

        // 医生不存在
        if (doctor == null) {
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }

        RecomDoctorBO doctorVO = new RecomDoctorBO();
        doctorVO.setHeadUrl(ImageUrlUtil.getImagePath(doctor.getHeadUrl()));
        doctorVO.setId(doctorId);// 医生ID
        doctorVO.setName(doctor.getName());// 医生姓名
        doctorVO.setPhone(doctor.getPhone());// 手机号
        // 职称
        if (doctor.getTitleId() != null) {
            doctorVO.setTitle(TitleUtil.getTitle(doctor.getTitleId()));
        }
        // 科室
        Integer departmentId = doctor.getDepartmentId();
        doctorVO.setDepartmentId(departmentId);

        int parentId = 0;
        if (departmentId != null) {
            if (departmentId == 0) {
                doctorVO.setDepartmentName(doctor.getCustomDepartment());// 自定义可是名
            } else {
                // 科室名称
                Department department = departmentDao.get(departmentId);
                doctorVO.setDepartmentName(department.getName());
                parentId = department.getParentId();
            }
        }

        //父级科室
        if (parentId != 0) {
            Department parentDepartment = departmentDao.get(parentId);
            if (parentDepartment != null) {
                doctorVO.setParentDepartmentId(parentId);
                doctorVO.setParentDepartmentName(parentDepartment.getName());
            }
        }

        Integer hospitalId = doctor.getHospitalId();
        if (hospitalId != null) {
            if (hospitalId == 0) {
                doctorVO.setHospitalName(doctor.getHospitalName());
            } else {
                Hospital hospital = hospitalDao.getHospitalById(hospitalId);
                doctorVO.setHospitalName(hospital.getName());
            }
        }

        RecordHospitalVO doctorRecordHospital = doctorRecordService.getDoctorRecordHospital(doctorId);
        doctorVO.setRecordHospitalId(doctorRecordHospital.getId());// 备案医院

        // 区域价格涉及城市id
        Integer priceCityId = getDoctorPriceCityId(doctorId, doctor);

        // 医生区域id
        Integer cityId = getDoctorAreaCityId(doctorId, doctor);

        doctorVO.setPriceCityId(priceCityId);
        doctorVO.setDoctorStatus(doctor.getStatus());// 医生认证状态
        doctorVO.setRecordStatus(doctor.getRecordStatus());// 医生备案状态
        doctorVO.setCityId(doctor.getCityId());// 城市ID

        //医生标签
        DoctorLabel label = new DoctorLabel(doctor.getLabelType());
        doctorVO.setLabel(DoctorLabelUtil.fromDoctorLabel(label));

        doctorVO.setPublic(label.getKeepRule());

        doctorVO.setExamine(false);// 健康检查标识
        doctorVO.setClinic(label.getClinic() ? 1 : 0);
        // 医嘱处方医生标识
        doctorVO.setRecordRecom(label.getRecordRecom());
        // 医生签章图片
        doctorVO.setSealImage(ConfigUtil.getString("img.hosts") + doctor.getSealImage());
        return doctorVO;
    }

    /**
     * 将字符串中的回车换行替换成空字符串 DoctorServiceImpl.handleBlank()
     *
     * @param sourceStr 需要替换的字符串
     * @return 替换后的字符串
     * @Date 2016年7月4日
     * @since 2.5.0
     */
    private String handleBlank(String sourceStr) {

        if (null != sourceStr) {
            sourceStr = sourceStr.trim().replaceAll("[\t ]+", " ").replaceAll("[\r\n]+", "\r\n");
        }
        return sourceStr;
    }

    /**
     * 根据医生id获取医生所在区域Id 如果没有返回全国
     *
     * @param doctorId
     * @return
     * @throws BusinessException
     * @Author fanjd
     * @Date 2017年9月11日
     * @see IDoctorService#getDoctorCityIdById(java.lang.Long)
     * @since 2.9.2
     */
    @Override
    public Integer getDoctorCityIdById(Long doctorId) throws BusinessException {

        return getDoctorPriceCityId(doctorId, null);
    }

    /**
     * 根据医生id获取医生所在区域Id 如果没有返回全国 此方法仅供用于价格计算相关业务逻辑点使用，非此业务逻辑请自行编写新的方法 原因：此方法包含澳门特殊乡医价格相关医生业务逻辑 DoctorServiceImpl.getDoctorCityId()
     *
     * @param doctorId
     * @param doctor
     * @return
     * @throws BusinessException
     * @Author fanjd
     * @Date 2017年9月11日
     * @since 1.0.0
     */
    private Integer getDoctorPriceCityId(Long doctorId, Doctor doctor) throws BusinessException {
        Integer specialCityId = getDrSpecialCity(doctorId);
        if (null != specialCityId) {// 乡医
            return specialCityId;
        } else {// 医生或医生医院的城市id
            Integer cityId = getDoctorAreaCityId(doctorId, doctor);
            return cityId;
        }
    }

    /**
     * 查询乡医的区域id() DoctorServiceImpl.getDrSpecialCity()
     *
     * @param doctorId
     * @return
     * @Author lichaopi
     * @Date 2018年2月26日
     * @since 1.0.0
     */
    public Integer getDrSpecialCity(Long doctorId) {
        Map<Long, Integer> drSpecialCityInfo = doctorDao.getDrSpecialCity(doctorId.toString());
        Integer specialCityId = drSpecialCityInfo.get(doctorId);
        return specialCityId;
    }

    @Override
    public Integer getDoctorAreaCityId(Long doctorId, Doctor doctor) throws BusinessException {
        String doctorAreaKey = PropertyValueConstants.getDoctorAreaCache(doctorId);
        Integer cityId = (Integer) RedisUtils.get(doctorAreaKey);// 根据缓存key 拿到医生患者上级医生所在区域
        if (null == cityId) {
            // 缓存里为空 从数据库里获取
            // 根据医生id 查出医生信息
            // 如果医生对象为空需要校验医生的合法性
            if (doctor == null) {
                Doctor doctorDB = doctorDao.get(doctorId);
                if (doctorDB == null) {// 医生为空 返回异常
                    LOGGER.error("-医生不存在, doctorId={}", doctorId);
                    throw new BusinessException(ExceptionCodes.DOCTOR_NOT_EXIST);
                } else {
                    cityId = getDoctorAreaId(doctorDB);
                }
            } else {
                cityId = getDoctorAreaId(doctor);
            }

        }
        return cityId;
    }


    /**
     * 获取医生的区域信息 如果医生的hospital_id 为0 取医生的city_id 否则拿hospital的city_id
     *
     * @param doctor
     * @return
     * @throws BusinessException
     * @Author fanjd
     * @Date 2017年9月11日
     * @since 2.9.2
     */
    private Integer getDoctorAreaId(Doctor doctor) {
        Integer cityId = PropertyValueConstants.WHOLE_COUNTRY_AREA;// 城市id默认为全国
        // 如果医生的hospital_id 为0 取医生的city_id 否则拿hospital的city_id
        if (doctor.getHospitalId() != null && doctor.getHospitalId() != 0) {
            Hospital hospital = hospitalDao.getHospitalById(doctor.getHospitalId());
            if (hospital != null) {
                cityId = hospital.getCityId();
            }

        } else {
            if (doctor.getCityId() != null && doctor.getCityId() != 0) {
                // 医生的城市id不为空取医生的城市id否则取默认值
                cityId = doctor.getCityId();
            }
        }
        // 针对历史遗留数据 如果城市id为广东省 、河南省、山西省 传对应省会id
        // 区域id必须传到市这一级 除了市 对方不认
        cityId = cityService.getSecondCityId(cityId, 1);
        // 将医生所在区域加入缓存
        RedisUtils.set(PropertyValueConstants.getDoctorAreaCache(doctor.getId()), cityId, PropertyValueConstants.DOCTOR_AREA_EXPIRE);
        return cityId;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAccountStatus(Long doctorId, Integer status, String operator) {
        Doctor doctor = doctorDao.get(doctorId);
        doctor.setAccountStatus(status);
        doctor.setChangedAt(new Date());
        doctorDao.update(doctor);
        if (1 == status) {
            List<ConsultSession> consultSessionList = consultSessionRepository.findByDoctorIdAndEndTimeIsNull(doctorId);
            if(CollUtil.isEmpty(consultSessionList)){
                return;
            }
            ThreadPoolUtil.execute(() ->{
                consultSessionList.forEach(c -> {
                    try {
                        imFeginClient.finishSession(c.getDoctorId(), c.getPatientId(), ImConstants.SESSION_CLOSE_TRIGGER_SOURCE_DISABLED_DOCTOR);
                    } catch (BusinessException e) {
                        log.info("医生禁用结束患者会话出现异常 doctorId: {}, patientId: {}, error: {}", c.getDoctorId(), c.getPatientId(), e.getMessage());
                    }
                });
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAccountType(Long doctorId, Integer type, String operator) {
        Doctor doctor = doctorDao.get(doctorId);
        doctor.setType(type);
        doctor.setChangedAt(new Date());
        doctorDao.update(doctor);
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateDoctorInfo(DoctorParamVO doctorParamVO, String operator) throws BusinessException {
        boolean updated = false;
        Doctor doctor = doctorDao.get(doctorParamVO.getDoctorId());
        if (doctor == null) {
            LOGGER.warn("医生ID不存在 {}", doctorParamVO.getDoctorId());
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }
//        if (doctor.getStatus() == Doctor.AUTH_STATUS_SUCCESS) {
//            //已认证医生资料不允许修改
//            LOGGER.info("认证已通过，不允许修改资料 doctorParam:{}", doctorParamVO);
//            throw new BusinessException(ExceptionCodes.ILLEGAL_REQUEST);
//        }

        if (doctor.getStatus() != Doctor.AUTH_STATUS_SUCCESS && StringUtils.isNotBlank(doctorParamVO.getName())) {
            doctor.setName(doctorParamVO.getName());
            updated = true;
        }

        if (StringUtils.isNotBlank(doctorParamVO.getEmail())) {
            doctor.setEmail(doctorParamVO.getEmail());
            updated = true;
        }

        if (StringUtils.isNotBlank(doctorParamVO.getHeadUrl())) {
            doctor.setHeadUrl(doctorParamVO.getHeadUrl().replaceAll(imgDomain, ""));
            LicenceVO headImage = new LicenceVO();
            headImage.setDoctorId(doctorParamVO.getDoctorId());
            headImage.setType(DOCTOR_LICENCE_TYPE_AVATAR);
            List<String> urls = new ArrayList<>();
            urls.add(doctor.getHeadUrl());
            headImage.setUrls(urls);
            updateLicence(doctorParamVO.getDoctorId(), headImage, DOCTOR_LICENCE_TYPE_AVATAR);
            updated = true;
        }

        if (StringUtils.isNotBlank(doctorParamVO.getExpertise())) {
            doctor.setExpertise(doctorParamVO.getExpertise());
            updated = true;
        }

        if (StringUtils.isNotBlank(doctorParamVO.getIntroduction())) {
            doctor.setIntroduction(doctorParamVO.getIntroduction());
            updated = true;
        }

        if (doctorParamVO.getDepartmentId() != null) {
            Department department = departmentDao.get(doctorParamVO.getDepartmentId());
            if (department == null) {
                LOGGER.warn("修改医生信息，科室ID不存在, doctorId:{}, departmentId:{}", doctorParamVO.getDoctorId(), doctorParamVO.getDepartmentId());
                throw new BusinessException(ExceptionCodes.INVALID_REQUEST, "科室ID无效");
            }
            doctor.setDepartmentId(doctorParamVO.getDepartmentId());
            updated = true;
        }

        if (doctorParamVO.getHospitalId() != null) {
            Hospital hospital = hospitalDao.getHospitalById(doctorParamVO.getHospitalId());
            if (hospital == null) {
                LOGGER.warn("修改医生信息，医院ID不存在, doctorId:{}, hospitalId:{}", doctorParamVO.getDoctorId(), doctorParamVO.getHospitalId());
                throw new BusinessException(ExceptionCodes.INVALID_REQUEST, "医院ID无效");
            }
            doctor.setHospitalId(doctorParamVO.getHospitalId());
            doctor.setCityId(hospital.getCityId());
            updated = true;
        }

        if (doctorParamVO.getTitleId() != null) {
            doctor.setTitleId(doctorParamVO.getTitleId());
            updated = true;
        }

        if (StringUtils.isNotBlank(doctorParamVO.getSignImgUrl())) {
            String pre = ConfigUtil.getString("img.hosts");
            String filepath = doctorParamVO.getSignImgUrl().replaceAll(pre, "");
            doctor.setSealImageOrigin(filepath);

            // String sealPath = filepath;
            // // 非手写章不抠图，避免红色被当做杂色扣除
            // if (ESignUtils.isHandWriting()) {
            // sealPath = this.processSealImage(doctor.getId(), fileBasePre + filepath);
            // }
            String sealPath = this.processSealImage(doctor.getId(), fileBasePre + filepath);
            doctor.setSealImage(sealPath.replaceAll(fileBasePre, ""));
            updated = true;
        }
        if (doctorParamVO.getIdCard() != null) {
            String number = doctorParamVO.getIdCard().getNumber();
            if (null != number) {
                if (number.length() == 18 && number.toUpperCase().endsWith("*")) {
                    number = number.substring(0, 17) + "X";
                }
                // 校验身份证
                if (!IdcardUtil.isValidCard(number)) {
                    throw new BusinessException(ExceptionCodes.PARAM_ERROR_ID_CARD_NUM);
                }
                List<Licence> licences = licenceDao.getLicenceByTypeAndNumber(DOCTOR_LICENCE_TYPE_IDCARD, number);
                if (licences.size() > 1) {
                    throw new BusinessException(ExceptionCodes.DOCTOR_RECORD_INFO_ID_NUMBER_IS_EXISTS);
                }
                if (licences.size() == 1) {
                    if (!doctorParamVO.getDoctorId().equals(licences.get(0).getDoctorId())
                        && number.equals(licences.get(0).getNumber())) {
                        throw new BusinessException(ExceptionCodes.DOCTOR_RECORD_INFO_ID_NUMBER_IS_EXISTS);
                    }
                }
            }
            boolean changed = updateLicence(doctorParamVO.getDoctorId(), doctorParamVO.getIdCard(), Licence.DOCTOR_LICENCE_TYPE_IDCARD);
            updated = changed ? true : updated;
        }
        if (doctorParamVO.getEmCard() != null) {
            boolean changed = updateLicence(doctorParamVO.getDoctorId(), doctorParamVO.getEmCard(), Licence.DOCTOR_LICENCE_TYPE_EMCARD);
            updated = changed ? true : updated;
        }
        if (doctorParamVO.getMedical() != null) {
            boolean changed = updateLicence(doctorParamVO.getDoctorId(), doctorParamVO.getMedical(), Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
            updated = changed ? true : updated;
        }
        if (doctorParamVO.getQualificationCertificate() != null) {
            boolean changed = updateLicence(doctorParamVO.getDoctorId(), doctorParamVO.getQualificationCertificate(),
                Licence.DOCTOR_LICENCE_TYPE_QUALIFICATION_CERTIFICATE);
            updated = changed ? true : updated;
        }
        if (doctorParamVO.getTitleCertificate() != null) {
            boolean changed = updateLicence(doctorParamVO.getDoctorId(), doctorParamVO.getTitleCertificate(),
                Licence.DOCTOR_LICENCE_TYPE_TITLECERTIFICATE);
            updated = changed ? true : updated;
        }

        // 校验认证送审条件
        boolean canApplayAuth = verifyCanApplyAuth(doctor);
        if (canApplayAuth && updated) {
            doctor.setStatus(Doctor.AUTH_STATUS_AUDITING);
            LOGGER.info("CMS后台修改医生信息达到认证送审提交，自动提交认证审核 doctorId:{}, doctorName:{}", doctor.getId(), doctor.getName());
        }
        boolean canApplyRecord = verifyCanApplyRecord(canApplayAuth, doctor);
        if (canApplyRecord && updated) {
            if (doctor.getStatus() != Doctor.AUTH_STATUS_SUCCESS) {
                //已通过认证再次修改医生信息不会修改备案状态
                doctor.setRecordStatus(Doctor.RECORD_STATUS_EXAMINEING);
            }
            LOGGER.info("CMS后台修改医生信息达到备案送审提交，自动提交备案审核 doctorId:{}, doctorName:{}", doctor.getId(), doctor.getName());
        }
        if (updated) {
            doctor.setChangedAt(new Date());
            doctor.setChangedBy(operator);
            doctor.setEmail(doctorParamVO.getEmail());
            doctorDao.update(doctor);
            LOGGER.info("CMS后台修改医生信息, doctorId:{}, doctorName:{}", doctor.getId(), doctor.getName());
        }
    }

    private boolean updateLicence(Long doctorId, LicenceVO licenceVO, Integer type) {
        boolean updated = false;

        Licence licence = licenceService.getLicenceByType(doctorId, type);
        if (licence == null) {
            licence = new Licence(doctorId, type);
        }
        if (!StringUtils.isEmpty(licenceVO.getNumber())) {
            licence.setNumber(licenceVO.getNumber());
            updated = true;
        }

        List<String> urls = licenceVO.getUrls();
        if (urls != null && urls.size() > 0) {
            String urlStr = urls.stream().map(url -> url.replaceAll(imgDomain, "")).collect(Collectors.joining(","));
            licence.setUrl(urlStr);
            updated = true;
        }
        if (updated) {
            licenceService.saveOrUpdate(licence);
        }
        return updated;
    }

    private boolean verifyCanApplyAuth(Doctor doctor) {
        // 姓名、科室、职称、医院、擅长、头像、工作证、执业证(含编号)

        // 已通过审核或审核中不变更状态
        if (doctor.getStatus() == Doctor.AUTH_STATUS_SUCCESS || doctor.getStatus() == Doctor.AUTH_STATUS_AUDITING) {
            return false;
        }
        boolean baseInfoHasNull = PublicService.paramsHasNull(doctor.getName(), doctor.getDepartmentId(), doctor.getHospitalId(), doctor.getTitleId(),
            doctor.getExpertise(),
            doctor.getHeadUrl());
        if (baseInfoHasNull) {
            return false;
        }

        Map<Integer, LicenceVO> licencesMap = licenceService.getLicencesByType(doctor.getId(), Licence.DOCTOR_LICENCE_TYPE_EMCARD,
            Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
        if (licencesMap.size() < 2) {
            return false;
        }

        LicenceVO emcard = licencesMap.get(Licence.DOCTOR_LICENCE_TYPE_EMCARD);
        if (emcard == null || emcard.getUrls() == null || emcard.getUrls().size() < 1 || StringUtils.isBlank(emcard.getUrls().get(0))) {
            return false;
        }

        LicenceVO medical = licencesMap.get(Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
        if (StringUtils.isBlank(medical.getNumber())) {
            return false;
        }
        if (medical == null || medical.getUrls() == null || medical.getUrls().size() < 1 || StringUtils.isBlank(medical.getUrls().get(0))) {
            return false;
        }

        return true;
    }

    private boolean verifyCanApplyRecord(boolean canApplyAuth, Doctor doctor) {
        // 身份证、职称证、资格证、电子签名

        if (doctor.getStatus() != Doctor.AUTH_STATUS_SUCCESS && doctor.getStatus() != Doctor.AUTH_STATUS_AUDITING) {
            if (!canApplyAuth) {
                return false;
            }
        }

        if (StringUtils.isBlank(doctor.getSealImage())) {
            return false;
        }

        Map<Integer, LicenceVO> licencesMap = licenceService.getLicencesByType(doctor.getId(), Licence.DOCTOR_LICENCE_TYPE_IDCARD,
            Licence.DOCTOR_LICENCE_TYPE_TITLECERTIFICATE,
            Licence.DOCTOR_LICENCE_TYPE_QUALIFICATION_CERTIFICATE);
        if (licencesMap.size() < 3) {
            return false;
        }

        LicenceVO idcard = licencesMap.get(Licence.DOCTOR_LICENCE_TYPE_IDCARD);
        if (idcard == null || idcard.getUrls() == null || idcard.getUrls().size() < 2) {
            return false;
        }
        if (StringUtils.isBlank(idcard.getUrls().get(0)) || StringUtils.isBlank(idcard.getUrls().get(1))) {
            return false;
        }
        if (StringUtils.isBlank(idcard.getNumber())) {
            return false;
        }

        LicenceVO title = licencesMap.get(Licence.DOCTOR_LICENCE_TYPE_TITLECERTIFICATE);
        if (title == null || title.getUrls() == null || title.getUrls().size() < 1 || StringUtils.isBlank(title.getUrls().get(0))) {
            return false;
        }

        LicenceVO qc = licencesMap.get(Licence.DOCTOR_LICENCE_TYPE_QUALIFICATION_CERTIFICATE);
        if (qc == null || qc.getUrls() == null || qc.getUrls().size() < 1 || StringUtils.isBlank(qc.getUrls().get(0))) {
            return false;
        }
        if (StringUtils.isBlank(qc.getNumber())) {
            return false;
        }

        return true;
    }

    @Override
    public String processSealImage(Long doctorId, String sourceImgPath) {
        String sealPath = "";
        try {
            sealPath = SEAL_PATH_PRE_DR + doctorId + "_" + System.currentTimeMillis() + ".png";
            boolean res = ImageAlphaUtil.transferAlpha(sourceImgPath, sealPath, 150);
            if (!res) {
                sealPath = null;
            }
        } catch (Exception e) {
            LOGGER.error("签章图片背景透明化处理异常", e);
        }
        return sealPath;
    }

    @Override
    public void perfectSealImage(String sealImage, Long doctorId) throws BusinessException {
        Doctor doctor = doctorDao.get(doctorId);
        doctor.setSealImage(sealImage);
        doctor.setChangedAt(new Date());
        doctorDao.update(doctor);
    }

    @Override
    public Map<Long, DoctorBO> getDoctorsByIds(List<Long> doctorIds) throws BusinessException {
        List<Doctor> list = doctorDao.getDoctors(doctorIds);
        List<Licence> licenceList =
            licenceRepository.findByTypeAndDoctorIdIn(Licence.DOCTOR_LICENCE_TYPE_IDCARD, doctorIds);
        Map<Long, String> licenceMap =
            licenceList.stream().collect(Collectors.toMap(Licence::getDoctorId, Licence::getNumber, (a, b) -> b));
        return Optional.ofNullable(list).orElse(Lists.newArrayList()).stream().map(s -> {
            DoctorBO doctorBO = new DoctorBO();
            doctorBO.setId(s.getId());
            doctorBO.setName(s.getName());
            doctorBO.setIdCard(licenceMap.get(s.getId()));
            doctorBO.setDepartmentId(s.getDepartmentId());
            return doctorBO;
        }).collect(Collectors.toMap(DoctorBO::getId, s -> s));
    }

    @Override
    public PcUserBO getUserInfo(String phone) {
        Doctor doctor = doctorDao.getDoctorByPhone(phone);
        List<Login> logins = loginRepository.findByPhoneNum(phone);
        Login login = null;
        if (CollectionUtil.isNotEmpty(logins)) {
            login = logins.get(0);
        }
        if (null != doctor && null != login) {
            PcUserBO bo = PcUserBO.builder()
                    .password(login.getPassword())
                    .phone(doctor.getPhone())
                    .userId(doctor.getId())
                    .name(doctor.getName())
                    .salt(login.getSalt())
                    .authFlag(Doctor.AUTH_STATUS_SUCCESS == doctor.getStatus())
                    .accountStatus(doctor.getAccountStatus())
                    .build();
            log.info("获取到的用户信息为 {}", bo.toString());
            return bo;
        }
        return null;
    }

    @SneakyThrows
    @Override
    public PcUserBO getUserFaceInfo(String phone, String h5SuccessUrl, String h5FailUrl) {
        Doctor doctor = doctorDao.getDoctorByPhone(phone);
        List<Login> logins = loginRepository.findByPhoneNum(phone);
        Login login = null;
        if (CollectionUtil.isNotEmpty(logins)) {
            login = logins.get(0);
        }
        if (null != doctor && null != login) {
            boolean authFlag = Doctor.AUTH_STATUS_SUCCESS == doctor.getStatus();
            PcUserBO pcUserBO = PcUserBO.builder()
                    .password(login.getPassword())
                    .phone(doctor.getPhone())
                    .userId(doctor.getId())
                    .name(doctor.getName())
                    .salt(login.getSalt())
                    .authFlag(authFlag)
                    .accountStatus(doctor.getAccountStatus())
                    .errCode(ExceptionCodes.LOGIN_NEED_VERIFY_AUTH)
                    .build();
            if (authFlag) {
                Licence licence = licenceService.getLicenceByType(doctor.getId(), Licence.DOCTOR_LICENCE_TYPE_IDCARD);
                // 查询实名认证结果，如未查询到，则提示实名
                AdTencentCloudSdkUtil.FaceSyncVO faceSync = this.tencentDetectService.getH5FaceSync(doctor.getId());
                if (!faceSync.getResult()) {
                    LOGGER.warn("当前账户登录需进行实名认证 doctorId:{}", doctor.getId());
                    TencentFaceIdVO faceIdVO = this.tencentDetectService.getH5FaceId(doctor.getId() + "", licence.getNumber(), doctor.getName());
                    BeanUtil.copyProperties(faceIdVO, pcUserBO);
                    pcUserBO.setCheckResult(false);
                } else {
                    pcUserBO.setCheckResult(true);
                }
            }

            log.info("获取到的用户人脸信息为 {}", pcUserBO.toString());
            return pcUserBO;
        }
        return null;
    }

    /**
     * 获取医生端我的主页信息 医生端1.1优化需求
     *
     * @param doctorId
     * @return
     * @throws BusinessException
     */
    @Override
    public DoctorMineVO getDoctorMineInfo(Long doctorId) throws BusinessException {
        DoctorMineVO mineVO = patientFeignClient.getDoctorDetailInfo(doctorId);
        return mineVO;
    }

    /**
     * 医生端1.1优化 资料编辑
     *
     * @param doctorId
     * @param introduction
     * @param expertise
     * @throws BusinessException
     */
    @Override
    public void updateMineInfo(Long doctorId, String introduction, String expertise) throws BusinessException {
        Doctor doctorDB = doctorDao.get(doctorId);
        if (doctorDB == null) {
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }
        doctorDB.setIntroduction(introduction);
        doctorDB.setExpertise(expertise);
        doctorDB.setChangedAt(new Date());
        doctorDao.update(doctorDB);
    }

    @Override
    public void sendAuditDoctorNotice() {
        LocalDateTime end = LocalDateTime.now();
        String endStr = end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 当前时间往前30分钟
        LocalDateTime start = end.minusMinutes(30);
        String startStr = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        List<Doctor> doctors = doctorRepository
            .findByStatusAndChangedAtBetween(Doctor.AUTH_STATUS_AUDITING, Date.from(start.atZone(ZoneId.systemDefault()).toInstant()),
                Date.from(end.atZone(ZoneId.systemDefault()).toInstant()));

        if (CollectionUtil.isNotEmpty(doctors)) {
            // 存在待审核医生，发送短信通知
            Integer num = doctors.size();
            Integer endIndex = endStr.indexOf(" ");
            String endParam = endStr.substring(endIndex + 1, endStr.length());
            Integer startIndex = startStr.indexOf(" ");
            String startParam = startStr.substring(startIndex + 1, startStr.length());

            String timeParam = startParam + "~" + endParam;
            LOGGER.info("时间范围 {}，获取到待审核医生数 {}", timeParam, doctors.size());

            String phones = ConfigUtil.getString("send.audit.doctor.notice.phone");
            if (null != phones) {
                Arrays.stream(phones.split(",")).forEach(e -> {
                    SMSUtil.sendSMS(e, SMSUtil.Template.SEND_AUDIT_DOCTOR_NOTICE_TEMPLATE, startParam, endParam, num);
                });
            }
        }
    }

    /**
     * 处方详情中医生信息
     * @param doctorId
     * @return
     */
    @Override
    public RecomDoctorBO getRecomDoctorInfo(Long doctorId) throws BusinessException {
        RecomDoctorBO doctorVO = new RecomDoctorBO();
        // 获取医生
        Doctor doctor = doctorDao.get(doctorId);
        if (doctor != null){
            doctorVO.setHeadUrl(ImageUrlUtil.getImagePath(doctor.getHeadUrl()));
            doctorVO.setId(doctorId);// 医生ID
            doctorVO.setName(doctor.getName());// 医生姓名
            doctorVO.setPhone(doctor.getPhone());// 手机号
            // 职称
            if (doctor.getTitleId() != null) {
                doctorVO.setTitle(TitleUtil.getTitle(doctor.getTitleId()));
            }
            // 科室
            Integer departmentId = doctor.getDepartmentId();
            doctorVO.setDepartmentId(departmentId);

            int parentId = 0;
            if (departmentId != null) {
                if (departmentId == 0) {
                    doctorVO.setDepartmentName(doctor.getCustomDepartment());// 自定义可是名
                } else {
                    // 科室名称
                    Department department = departmentDao.get(departmentId);
                    doctorVO.setDepartmentName(department.getName());
                    parentId = department.getParentId();
                }
            }

            //父级科室
            if (parentId != 0) {
                Department parentDepartment = departmentDao.get(parentId);
                if (parentDepartment != null) {
                    doctorVO.setParentDepartmentId(parentId);
                    doctorVO.setParentDepartmentName(parentDepartment.getName());
                }
            }

            Integer hospitalId = doctor.getHospitalId();
            if (hospitalId != null) {
                if (hospitalId == 0) {
                    doctorVO.setHospitalName(doctor.getHospitalName());
                } else {
                    Hospital hospital = hospitalDao.getHospitalById(hospitalId);
                    doctorVO.setHospitalName(hospital.getName());
                }
            }

            RecordHospitalVO doctorRecordHospital = doctorRecordService.getDoctorRecordHospital(doctorId);
            doctorVO.setRecordHospitalId(doctorRecordHospital.getId());// 备案医院
            // 区域价格涉及城市id
            Integer priceCityId = getDoctorPriceCityId(doctorId, doctor);
            doctorVO.setPriceCityId(priceCityId);
            doctorVO.setDoctorStatus(doctor.getStatus());// 医生认证状态
            doctorVO.setRecordStatus(doctor.getRecordStatus());// 医生备案状态
            doctorVO.setCityId(doctor.getCityId());// 城市ID
            //医生账户状态0:正常，1:禁用
            doctorVO.setAccountStatus(doctor.getAccountStatus());
            //医生标签
            DoctorLabel label = new DoctorLabel(doctor.getLabelType());
            doctorVO.setLabel(DoctorLabelUtil.fromDoctorLabel(label));

            doctorVO.setPublic(label.getKeepRule());

            doctorVO.setExamine(false);// 健康检查标识
            doctorVO.setClinic(label.getClinic() ? 1 : 0);
            // 医嘱处方医生标识
            doctorVO.setRecordRecom(label.getRecordRecom());
            // 医生签章图片
            doctorVO.setSealImage(ConfigUtil.getString("img.hosts") + doctor.getSealImage());
        }

        return doctorVO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchSaveDoctor(List<DoctorInfoExcelVO> doctorInfos) throws BusinessException {

        // ArrayList<Doctor> doctorList = new ArrayList<>();
        HashMap<String, Doctor> doctorHashMap = new HashMap<>(); // 医生map~统一处理
        ArrayList<Login> loginList = new ArrayList<>();
        for (DoctorInfoExcelVO vo : doctorInfos) { // 统一封装校验
            // 1. 验证手机是否存在 2.判断手机号的非空和格式
            this.validPhone(vo.getPhone());
            // 关联id为0
            Long relationId = 0L;
            LoginParamBO loginParamBO = new LoginParamBO(vo.getPhone(), vo.getName(), DoctorInfoExcelVO.DEFAULT_PWD,
                    LoginParamBO.RELATION_TYPE_PERSONAL, relationId, 5, false);
            Login login = this.checkLogin(loginParamBO); // 封装login信息
            loginList.add(login);
            // 4.封装医生注册信息
            Doctor doctor = this.saveLoginInfo(login, vo.getName());
            doctor.setTitleId(vo.getTitleId());// 职称
            doctor.setDepartmentId(vo.getDepartmentId());// 科室
            doctor.setHospitalId(vo.getHospital());// 执业医院
            doctor.setCertNo(vo.getDoctorCertNum());// 资格证书
            doctor.setIntroduction(vo.getIntroduction());// 医生简介
            doctor.setExpertise(vo.getExpertise());// 擅长(专业与专长)
            doctorHashMap.put(vo.getPhone(), doctor);
        }
        for (Login login : loginList) {// 统一持久化处理
            login.setLastPwdModify(new Date());
            Long loginId = this.loginDao.saveLogin(login);
            Doctor doctor = doctorHashMap.get(login.getPhoneNum());
            doctor.setUserId(loginId);
            Long doctorId = this.doctorDao.save(doctor);
            this.loginService.initRegist(doctorId, doctor.getPhone());
            // 初始化已开启收费项目
            this.drChargeItemsService.saveInitServiceItems(doctor);
        }

    }

    @Override
    public String saveNumber(Long doctorId, String number, Integer type) throws BusinessException {
        if (type == 0) {
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        if (type == 4) {
            if (!IdcardUtil.isValidCard(number)) {
                throw new BusinessException(ExceptionCodes.PARAM_ERROR_ID_CARD_NUM);
            }
        }
        Licence licence = licenceService.getLicenceByType(doctorId, type);
        if (null == licence) {
            licence = new Licence(doctorId, type);
        }
        licence.setNumber(number);
        licence.setChangedAt(new Date());
        licence.setCreatedBy("SYSTEM");
        licenceService.saveOrUpdate(licence);
        return number;
    }

    @Override
    public SignUserParamVO getUserByPhone(String phone) throws BusinessException {
        Doctor doctor = doctorRepository.findFirstByPhone(phone);
        Licence licence = licenceDao.getLicenceByType(doctor.getId(), Licence.DOCTOR_LICENCE_TYPE_IDCARD);
        String pwd = licence.getNumber().substring(licence.getNumber().length() - 4).toUpperCase();
        return SignUserParamVO.builder().userId(doctor.getUserId()).name(doctor.getName()).idCard(licence.getNumber())
            .phone(doctor.getPhone()).pwd(pwd).hospital(HOSPITAL_NAME).sealImagePath(doctor.getSealImage()).build();
    }

    @Override
    public DoctorDetailBO getDoctorDetailById(Long doctorId) throws BusinessException {
        UserCenterVO userCenter = getUserCenter(doctorId);
        if (userCenter == null) {
            throw new BusinessException(BaseExceptionCodes.PARAM_ERROR);
        }
        // 基础信息
        DoctorDetailBO doctorDetailBO = new DoctorDetailBO();
        doctorDetailBO.setName(userCenter.getName());
        doctorDetailBO.setDepartment(userCenter.getDepartment());
        doctorDetailBO.setHeaderImageUrl(userCenter.getHeaderImageUrl());
        doctorDetailBO.setHospitalName(userCenter.getHospitalName());
        doctorDetailBO.setPhone(userCenter.getPhone());
        doctorDetailBO.setTitle(userCenter.getTitle());

        // 收费信息
        List<ChargeVO> chargeInfo = chargeService.getChargeInfo(doctorId);
        Map<Integer, Integer> chargeMap =
            chargeInfo.stream().collect(Collectors.toMap(ChargeVO::getType, ChargeVO::getCharge));
        doctorDetailBO.setConsultMessageCharge(chargeMap.get(1));
        doctorDetailBO.setConsultVideoCharge(chargeMap.get(2));

        // 总患者数
        int totalPatientCount = patientPersonDoctorRepository.getTotalPatientCount(doctorId);
        doctorDetailBO.setPatientTotal(totalPatientCount);

        // 计算好评率
        List<DrConsultComment> comments = drConsultCommentRepository.findByDoctorId(doctorId);
        if (CollUtil.isEmpty(comments)) {
            doctorDetailBO.setPraiseRate("100%");
        } else {
            int sum = comments.stream().mapToInt(DrConsultComment::getStarLevel).sum();
            int praiseRate = (sum / comments.size() * 2) * 10;
            doctorDetailBO.setPraiseRate(praiseRate + "%");
        }
        doctorDetailBO.setComplainCnt(0);
        return doctorDetailBO;
    }

    private Login checkLogin(LoginParamBO paramBO) {
        // TODO paramBO 增加参数校验
        String phoneNum = paramBO.getPhone();
        String pwd = paramBO.getPwd();
        Integer relationType = paramBO.getRelationType();
        Long relationId = paramBO.getRelationId();
        String name = paramBO.getName();
        // boolean registH5Flag = false;
        // String platform = paramBO.getPlatform();

        // 2.生成唯一的盐和邀请码
        String salt = null;
        String recommCode = null;
        do {
            salt = GengerCode.generateSaltCode();
            recommCode = GengerCode.generateDocCode();
        } while (this.findExsitSaltRecomm(salt, recommCode));
        // 3.并封装用户的注册信息 salt:平台 recommcode:邀请码
        Login login =
            this.validLogin(phoneNum, pwd, relationType, relationId, salt, recommCode, paramBO.getRegistSouce());

        return login;
    }

    // 封装登录用户信息
    private Login validLogin(String phoneNum, String pwd, Integer relationType, Long relationId, String salt,
        String recommCode, Integer registSource) {

        Login login = new Login();
        login.setPhoneNum(phoneNum);
        login.setPassword(MD5.md5(pwd + salt));
        login.setType(relationType);
        login.setRelationId(relationId);
        login.setSalt(salt);
        login.setRecommCode(recommCode);
        login.setRegType(registSource);
        login.setCreatedAt(new Date(System.currentTimeMillis()));
        login.setLastPwdModify(new Date());
        return login;
    }

    // 封装医生信息
    private Doctor saveLoginInfo(Login login, String name) throws BusinessException {
        if (login == null) {
            LOGGER.warn("login对象为空");
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }

        // 创建登陆对象
        // Long loginId = this.loginDao.saveLogin(login);
        Doctor doctor = new Doctor();
        // 设置用户id
        if (name != null) {
            doctor.setName(name.trim());
        } else {
            doctor.setName("互联网医院");
        }
        // doctor.setUserId(loginId);
        doctor.setPhone(login.getPhoneNum());
        doctor.setRecommCode(login.getRecommCode());
        doctor.setStatus(Doctor.AUTH_STATUS_NOT_PERFECTION);
        doctor.setAccountStatus(0);
        doctor.setLabelType(Doctor.DOCTOR_LABLE_NOMAL);
        doctor.setCreatedAt(new Date());
        doctor.setType(PropertyValueConstants.DOCTOR_TYPE_NOMAL);
        doctor.setRecordStatus(Doctor.RECORD_STATUS_NOT_APPLY);
        // Long doctorId = this.doctorDao.save(doctor);
        // 初始化已开启收费项目
        // this.drChargeItemsService.saveInitServiceItems(doctor);

        return doctor;
    }

    private boolean findExsitSaltRecomm(String salt, String recommCode) {
        return this.loginDao.findExsitSaltRecomm(salt, recommCode);
    }

    /**
     * 验证手机号根式和非空/是否存在
     *
     * @param phoneNum
     *
     * @throws BusinessException
     * @Author chenlin
     * @Date 2016年3月25日
     * @since 1.0.0
     */
    private void validPhone(String phoneNum) throws BusinessException {
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        if (!StringUtil.isMobileNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_ERROR);
        }
        // 电话是已经存在
        boolean exsit = loginService.findCountByPhoneNum(phoneNum);
        if (exsit) {
            throw new BusinessException(ExceptionCodes.REGIST_EXSIT);
        }
    }
}
